package org.feng.repository;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.core.query.Update;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * R2dbcEntityTemplate 的数据库操作：不区分表，统一处理
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2022年08月16日 09时51分
 */
@Slf4j
@Component
public class R2dbcEntityTemplateRepository {
    @Resource
    private R2dbcEntityTemplate r2dbcEntityTemplate;

    /**
     * 按照指定条件更新数据表
     *
     * @param entity   实体；类需要使用 {@link Table} 注解、字段属性需要使用 {@link Column}注解、主键使用{@link org.springframework.data.annotation.Id}标注
     * @param paramMap 为空时自动获取当前实体对象不为空的字段进行更新；不为空时，表示要更新的字段
     * @param criteria 条件
     * @param <T>      任一数据表对应的实体类类型
     * @return 数据库操作影响的行数
     */
    public <T> Mono<Integer> update(@Nonnull T entity, @Nullable Map<SqlIdentifier, Object> paramMap, @Nonnull Criteria criteria) {
        Class<?> entityClass = entity.getClass();
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
            if (entityClass.isAnnotationPresent(Table.class)) {
                Field[] declaredFields = entityClass.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (declaredField.isAnnotationPresent(Column.class)) {
                        Column column = declaredField.getAnnotation(Column.class);
                        String columnName = column.value();
                        String fieldName = declaredField.getName();
                        String methodName = generateGetMethodName(fieldName);
                        try {
                            Object fieldValue = entityClass.getMethod(methodName).invoke(entity);
                            // 列-参数值
                            if (Objects.nonNull(fieldValue)) {
                                paramMap.put(SqlIdentifier.unquoted(columnName), fieldValue);
                            }
                        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
        Update update = Update.from(paramMap);
        Query query = Query.query(criteria);
        return r2dbcEntityTemplate.update(entityClass).matching(query)
                .apply(update);
    }

    /**
     * 更新实体 entity 对应的数据库表：将不为空的字段进行更新。
     *
     * @param entity   实体
     * @param criteria 条件，例如条件为id=2时，写法如下：<code>Criteria.where("id").is("2")</code>
     * @param <T>      任一数据表对应的实体类类型
     * @return 数据库操作影响的行数
     */
    public <T> Mono<Integer> update(@Nonnull T entity, @Nonnull Criteria criteria) {
        return update(entity, null, criteria);
    }

    /**
     * 插入一条数据，对应的表示 entity 对应的表。
     *
     * @param entity 实体
     * @param <T>    任一数据表对应的实体类类型
     * @return 插入后的结果
     */
    public <T> Mono<T> insert(@Nonnull T entity) {
        return r2dbcEntityTemplate.insert(entity);
    }

    /**
     * 查询（0-n）条数据
     *
     * @param entityClass 实体
     * @param criteria    条件
     * @param <T>         任一数据表对应的实体类类型
     * @return （0-n）数据
     */
    public <T> Flux<T> select(@Nonnull Class<T> entityClass, @Nonnull Criteria criteria) {
        return r2dbcEntityTemplate.select(Query.query(criteria), entityClass);
    }

    /**
     * 生成 get方法名
     *
     * @param fieldName 变量名，不能为空
     * @return get方法名
     */
    private String generateGetMethodName(@Nonnull String fieldName) {
        return "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
    }
}
